use crate::capi::qbus;
use crate::qbus::QBus;
use crate::qdispatcher::{QdispatcherCb, QDispatcher};
use core::ffi::{c_uint, c_void};
use core::ptr::null_mut;
use embed_std::errorln;

#[repr(C)]
#[derive(Debug, Clone)]
pub struct qdispatcher {
    pub priv_: *mut c_void,
    pub free: Option<unsafe extern "C" fn(qd: *mut qdispatcher)>,
    pub broadcast: Option<
        unsafe extern "C" fn(
            qd: *mut qdispatcher,
            id: c_uint,
            data: *const c_void,
            data_len: c_uint,
        ),
    >,
    pub add_listener: Option<
        unsafe extern "C" fn(
            qd: *mut qdispatcher,
            id: c_uint,
            cb: QdispatcherCb,
            args: *mut c_void,
        ),
    >,
    pub remove_listener:
        Option<unsafe extern "C" fn(qd: *mut qdispatcher, id: c_uint, cb: QdispatcherCb)>,
}

impl Drop for qdispatcher {
    fn drop(&mut self) {
        if !self.priv_.is_null() {
            let q = unsafe { Box::from_raw(self.priv_ as *mut QDispatcher) };
            q.stop_dispatch();
            drop(q);
            self.priv_ = null_mut();
        }
    }
}

unsafe extern "C" fn qfree(qd: *mut qdispatcher) {
    if qd.is_null() {
        return;
    }
    let q = Box::from_raw(qd);
    drop(q);
}

macro_rules! SELF {
    ($qd:expr) => {{
        if $qd.is_null() {
            return;
        }
        let q = Box::leak(Box::from_raw($qd));
        if q.priv_.is_null() {
            return;
        }
        let qp = Box::leak(Box::from_raw(q.priv_ as *mut QDispatcher));
        qp
    }};
}

unsafe extern "C" fn qbroadcast(
    qd: *mut qdispatcher,
    id: c_uint,
    data: *const c_void,
    data_len: c_uint,
) {
    let qp = SELF!(qd);
    let data = if data.is_null() || data_len == 0 {
        &[]
    } else {
        core::slice::from_raw_parts(data as *const u8, data_len as usize)
    };
    if let Err(e) = qp.publish(id, data) {
        errorln!("publish failed: {}", e);
    }
}

unsafe extern "C" fn q_add_listener(
    qd: *mut qdispatcher,
    id: c_uint,
    cb: QdispatcherCb,
    args: *mut c_void,
) {
    let qp = SELF!(qd);
    qp.add_listener(id, cb, args);
}

unsafe extern "C" fn q_remove_listener(qd: *mut qdispatcher, id: c_uint, cb: QdispatcherCb) {
    let qp = SELF!(qd);
    qp.remove_listener(id, cb);
}

#[no_mangle]
unsafe extern "C" fn create_qdispatcher(bus: *mut qbus) -> *mut qdispatcher {
    if bus.is_null() {
        return null_mut();
    }
    let qb = Box::leak(Box::from_raw(bus as *mut qbus));
    if qb.priv_.is_null() {
        return null_mut();
    }
    let qbus = Box::leak(Box::from_raw(qb.priv_ as *mut QBus));
    let q = Box::new(QDispatcher::new(qbus.clone()));
    if let Err(e) = q.start_dispatch() {
        errorln!("start dispatch failed: {}", e);
        return null_mut();
    }
    let qd = Box::new(qdispatcher {
        priv_: Box::into_raw(q) as *mut c_void,
        free: Some(qfree),
        broadcast: Some(qbroadcast),
        add_listener: Some(q_add_listener),
        remove_listener: Some(q_remove_listener),
    });
    Box::into_raw(qd)
}
